package com.echat.serviceapigateway.security.config;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.support.NullValue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.*;

import java.io.IOException;
import java.time.Duration;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;

/**
 * Created by chenmohan on 2018/09/10
 *
 * @description SpringCache 配置类，使用jaskson的字符串序列化方式
 * 这里的安全模块和MessageSender模块都使用了Cache,缓存命名空间都直接在这个类下定义
 */
@Configuration
@Slf4j
public class CacheConfig {

    //安全模块使用到的Cache
    public static final String CACHE_USER = "auth:user:";
    public static final String CACHE_USER_ID = "auth:user_id:";
    public static final String CACHE_USERNAME = "auth:userName";
    public static final String CACHE_IP_WHITE_LIST = "auth:ip_whilte_list:";
    public static final String CACHE_TOKEN_GET_LIMIT = "auth:token_get_limit:";
    public static final String CACHE_USER_AUTH_TREE = "auth:tree:";
    public static final String CACHE_USER_AUTH_CODE = "auth:code:";
    public static final String CACHE_RESOURCE = "auth:resource:";
    public static final String CACHE_AUTHORIZED_URL = "auth:authUrl:";

    /** 1小时 */
    private static final Duration DURATION_1_HOURS = Duration.ofSeconds(3600);
    /** 1天 */
    private static final Duration DURATION_1_DAY = Duration.ofDays(1);
    /** 30分钟 */
    private static final Duration DURATION_HAFL_AN_HOURS = Duration.ofSeconds(1800);

    @Bean
    public RedisCacheManager cacheManager(StringRedisTemplate redisTemplate) throws ExecutionException, InterruptedException {
        RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
        ConcurrentHashMap<String, RedisCacheConfiguration> redisCacheConfigurationMap = new ConcurrentHashMap<>();
        //配置不同cacheName下key的TTL
        //secutriy service
        redisCacheConfigurationMap.put(CACHE_USER, createRedisCacheConfig().entryTtl(DURATION_1_HOURS));
        redisCacheConfigurationMap.put(CACHE_USER_ID, createRedisCacheConfig().entryTtl(Duration.ZERO));
        redisCacheConfigurationMap.put(CACHE_IP_WHITE_LIST, createRedisCacheConfig().entryTtl(DURATION_1_HOURS));
        redisCacheConfigurationMap.put(CACHE_TOKEN_GET_LIMIT, createRedisCacheConfig());
        redisCacheConfigurationMap.put(CACHE_USERNAME, createRedisCacheConfig().entryTtl(DURATION_1_HOURS));
        redisCacheConfigurationMap.put(CACHE_USER_AUTH_TREE, createRedisCacheConfig().entryTtl(Duration.ZERO));
        redisCacheConfigurationMap.put(CACHE_AUTHORIZED_URL, createRedisCacheConfig().entryTtl(Duration.ZERO)); //永久有效

        //admin
        redisCacheConfigurationMap.put(CACHE_RESOURCE, createRedisCacheConfig().entryTtl(DURATION_1_HOURS));
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
        log.info("初始化 CacheMamager");
        return new RedisCacheManager(redisCacheWriter, createRedisCacheConfig(), redisCacheConfigurationMap);
    }

    /**
     * RedisCache的默认实现，主要指定key和value的序列化方式,使用json
     *
     * @return
     */
    private RedisCacheConfiguration createRedisCacheConfig() {
        return RedisCacheConfiguration.defaultCacheConfig()
                .computePrefixWith(cacheName -> cacheName)
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer()));
    }

    private RedisSerializer<String> keySerializer() {
        return new StringRedisSerializer();
    }

    private RedisSerializer<Object> valueSerializer() {
        return new GenericJackson2JsonRedisSerializerEx();
    }

    /*
     *解决LocalDateTime序列化引发的异常而修改GenericJackson2JsonRedisSerializer代码
     */
    private class GenericJackson2JsonRedisSerializerEx implements RedisSerializer<Object> {
        protected GenericJackson2JsonRedisSerializer serializer = null;

        public GenericJackson2JsonRedisSerializerEx() {
            ObjectMapper om = new ObjectMapper();
            om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); //禁用时间类序列化
            om.registerModule(new JavaTimeModule());
            om.registerModule((new SimpleModule())
                    .addSerializer(new NullValueSerializer()));
            om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
            this.serializer = new GenericJackson2JsonRedisSerializer(om);
        }

        public GenericJackson2JsonRedisSerializerEx(ObjectMapper om) {
            this.serializer = new GenericJackson2JsonRedisSerializer(om);
        }

        @Override
        public byte[] serialize(Object o) throws SerializationException {
            return serializer.serialize(o);
        }

        @Override
        public Object deserialize(byte[] bytes) throws SerializationException {
            return serializer.deserialize(bytes);
        }

        protected class NullValueSerializer extends StdSerializer<NullValue> {
            private static final long serialVersionUID = 1999052150548658807L;
            private final String classIdentifier = "@class";

            NullValueSerializer() {
                super(NullValue.class);
            }

            public void serialize(NullValue value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
                jgen.writeStartObject();
                jgen.writeStringField(this.classIdentifier, NullValue.class.getName());
                jgen.writeEndObject();
            }
        }
    }

}
